import math
import os
import pandas as pd
import numpy as np
import datetime
import Core.Config as Config
import statsmodels.api as sm
import matplotlib.pyplot as plt
from SystematicFactors.General import Save_Systematic_Factor_To_Database


def hurstexp(x, d=50):
    """hurst计算函数 根据R中hurstexp移植 使用corrected empirical Hurst exponent算法 根据R中给的例子确定使用这个算法
    x:pandas.Series or np.array 传入的时间序列
    d:最小的box size 子区间最小的长度
        """

    def rssimple(x):
        # 简单算法 直接计算x的log_rs/log_n
        n = len(x)
        y = x - x.mean()
        s = y.cumsum()
        rs = (s.max() - s.min()) / x.std()  # 标准化极差
        return np.log(rs) / np.log(n)

    def rscalc(x, n):
        z = x.copy()
        m = len(z) // n

        y = z.reshape(m,n)
        e = y.mean(axis=1)
        s = y.std(axis=1,ddof=1)
        for i in range(m):
            y[i, :] = y[i, :] - e[i]
        y = y.cumsum(axis=1)
        mm = y.max(axis=1) - y.min(axis=1)
        return np.mean(mm / s)

    def divisors(n, n0=2):
        # 从n0到n/2 可等分的子区间长度
        n0n = np.array(range(n0, int(np.floor(n / 2)) + 1))
        dvs = n0n[n % n0n == 0]
        return dvs

    x = np.array(x)
    d = np.max([2, int(np.floor(d))])
    N = len(x)
    if (N % 2 != 0):
        x = np.append(x, (x[N - 2] + x[N - 1]) / 2)

    # 本段为了选取合适的N的长度的对应的等分区间  选取可等分区间最多的N
    N = len(x)
    dmin = d  # 此处的d为子区间最小的长度
    N0 = min([int(np.floor(0.99 * N)), N - 1])
    N1 = N0
    dv = divisors(N1, dmin)
    for i in range(N0 + 1, N + 1):
        dw = divisors(i, dmin)
        if (len(dw) > len(dv)):
            N1 = i
            dv = dw

    OptN = N1
    d = dv  # 此处的d为确定下来的子区间长度列表
    x = x[0:OptN]  # 取到OptN

    N = len(d)
    RSe =np.array([])
    ERS =np.array([])
    for i in range(N):
        RSe = np.append(RSe,rscalc(x, d[i]))
    for i in range(N):
        n = d[i]
        K = np.array(range(n-1,0,-1))/np.array(range(1,n))
        ratio = (n - 0.5) / n * sum(np.sqrt(K))
        if n > 340:
            ERS = np.append(ERS, ratio / np.sqrt(0.5 * np.pi * n))
        else:
            ERS = np.append(ERS, (math.gamma(0.5 * (n - 1)) * ratio) / (math.gamma(0.5 * n) * np.sqrt(np.pi)))

    ERSal = np.sqrt(0.5 * np.pi * d)
    Pal = np.polyfit(np.log10(d), np.log10(RSe - ERS + ERSal), 1)
    Hal = Pal[0]
    Hs = rssimple(x)
    return Hal


def Calc_Hurst(database, datetime2, period="Weekly"):
    #
    datetime1 = datetime.datetime(2005, 1, 1)
    df = database.Get_Daily_Bar_DataFrame("000300.SH",
                                          instrument_type="index",
                                          projection=["date", "close"],
                                          datetime1=datetime1,
                                          datetime2=datetime2)

    df.index = pd.to_datetime(df["date"])
    df_weekly = df.resample('W').last()
    df_weekly.dropna(inplace=True)
    df_weekly = df_weekly[["close"]]
    #
    lgIndexHurst = df_weekly['close'].map(np.log).diff().dropna()
    gap = 51
    dd = 2
    lgHP = lgIndexHurst.rolling(window=gap).apply(hurstexp, kwargs={'d': dd})
    df_factor = lgHP.to_frame(name="Hurst")

    # 原版中，周度数据（HS300）减去了0.5
    # 月度数据（Bond）没有减去了0.5
    if period == "Monthly":
        df_factor = df_factor.resample('M').last().fillna(0)
    else:
        df_factor = df_factor.resample('W').last().fillna(0)

    df_factor = df_factor - 0.5
    #
    # print(df_factor)
    # df_factor.plot(y="Hurst")
    # plt.show()

    Save_Systematic_Factor_To_Database(database, df_factor, save_name="Hurst_" + period, field_name="Hurst")


if __name__ == '__main__':
    #
    # from Core.Config import *
    # pathfilename = os.getcwd() + "\..\Config\config2.json"
    # config = Config(pathfilename)
    # database = config.DataBase("JDMySQL")
    # realtime = config.RealTime()
    #
    path_filename = os.getcwd() + "\..\Config\config_local.json"
    database = Config.create_database(database_type="MySQL", config_file=path_filename, config_field="MySQL")
    #
    datetime1 = datetime.datetime(2000, 1, 1)
    datetime2 = datetime.datetime(2024, 4, 30)
    #
    Calc_Hurst(database, datetime2, period="Weekly")
    Calc_Hurst(database, datetime2, period="Monthly")